// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package base

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"
)

var poolItem = &sync.Pool{
	New: func() any {
		return &Item{}
	},
}

// Item
// is a component from a log entry.
type Item struct {
	Field map[string]any
	Level Level
	Text  string
	Time  time.Time
	Stack Stack

	// OpenTracing & OpenTelemetry.

	SpanId, ParentSpanId             *SpanId
	SpanPosition, ParentSpanPosition int32
	TraceId                          *TraceId
}

// NewItem
// creates a new item entry based on log level and contents.
func NewItem(ctx context.Context, field map[string]any, level Level, format string, args ...any) *Item {
	// Acquires
	// an entry from pool and init fields value.
	item := poolItem.Get().(*Item)
	item.Field = field
	item.Level = level
	item.Text = fmt.Sprintf(format, args...)
	item.Time = time.Now()

	// Bind context.
	item.With(ctx)

	// Stack
	// created for fatal level log.
	if level.IsFatal() {
		item.Stack = (&stack{}).init()
	}
	return item
}

// Copy
// an item with current fields value.
func (o *Item) Copy() *Item {
	item := poolItem.Get().(*Item)
	item.Field = o.Field
	item.Level = o.Level
	item.Text = o.Text
	item.Time = o.Time
	item.Stack = o.Stack
	item.SpanId, item.ParentSpanId = o.SpanId, o.ParentSpanId
	item.SpanPosition, item.ParentSpanPosition = o.SpanPosition, o.ParentSpanPosition
	item.TraceId = o.TraceId
	return item
}

// JsonField
// converts the log fields to json string.
func (o *Item) JsonField() string {
	if buf, err := json.Marshal(o.Field); err == nil {
		return string(buf)
	}
	return ""
}

// Release
// put the item entry to pool.
func (o *Item) Release() {
	o.Field, o.Stack = nil, nil
	o.Text = ""
	o.SpanId, o.ParentSpanId, o.TraceId = nil, nil, nil
	o.SpanPosition, o.ParentSpanPosition = 0, 0
	poolItem.Put(o)
}

// String
// converts the log entry as a string with stack traces.
func (o *Item) String() (str string) {
	str = o.Text
	if o.Level.IsFatal() && o.Stack != nil {
		str += "\n" + o.Stack.String()
	}
	return
}

// With
// bind context on entry.
func (o *Item) With(ctx context.Context) *Item {
	if ctx != nil {
		if v, ok := ctx.Value(TracingOnContext).(*Tracing); ok {
			o.ParentSpanId, o.ParentSpanPosition = v.ParentSpanId, v.ParentSpanPosition
			o.SpanId, o.TraceId = v.SpanId, v.TraceId
			o.SpanPosition, _ = v.Increment()
		}
	}
	return o
}
